#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/spi.h"

#include "wizchip_conf.h"
#include "bsp_spi.h"
#include "socket.h"
#include "dhcp.h"
#include "MQTTClient.h"

#define SOCKET_ID 0
#define ETHERNET_BUF_MAX_SIZE (1024 * 2)

#define MQTT_SEND_BUFF_SIZE 2048 /*Send cache size*/
#define MQTT_RECV_BUFF_SIZE 2048 /*Recv cache size*/
#define DHCP_RETRY_COUNT 5               // DHCP retry times
#define ETHERNET_BUF_MAX_SIZE (1024 * 2) // Send and receive cache size

enum status
{
    CONN= 0,  
    SUB,
    PUB_ONLINE,
    KEEPALIVE,
    ERROR = 255,
} run_status;


uint8_t mqtt_send_buff[MQTT_SEND_BUFF_SIZE] = {0};
uint8_t mqtt_recv_buff[MQTT_RECV_BUFF_SIZE] = {0};


typedef struct MQTTCONNECTION
{
    char mqttHostUrl[1024]; /*Server URL*/
    int port;               /*Server port number*/
    char clientid[1024];    /*client ID*/
    char username[1024];    /*user name*/
    char passwd[1024];      /*user passwords*/
    uint8_t server_ip[4];   /*Server IP*/
    char pubtopic[255];     /*publication*/
    char subtopic[255];    /*subscription*/
    int pubQoS;               /* publishing messages*/
    int subQoS;              /* subscription messages*/
    char willtopic[255];      /*Will topic  */ 
    int willQoS;                /*Will message  */ 
    char willmsg[255];         /*Will */ 
} mqttconn;

mqttconn mqtt_params = {
    .server_ip = {54,244,173,190},               /*Define the Connection Server IP*/
    .port = 1883,                                   /*Define the connection service port number*/
    .clientid = "hxrzyBEzRJz.MQTT1|securemode=2,signmethod=hmacsha256,timestamp=1698648840189|", /*Define the client ID*/
    .username = "MQTT1&hxrzyBEzRJz",                    /*Define the user name*/
    .passwd = "21733721029051630f5f86efa6860403cbc68cda27223f83ec9175f69243ee93",                             /*Define user passwords*/
    .pubtopic = "W5100S_pubtopic",                             /*Define the publication message*/
    .subtopic= "W5100S_subtopic",                            /* vDefine subscription messages*/
    .pubQoS = 0,                                       /*Defines the class of service for publishing messages*/
    .willtopic = "W5100S_will",                         /*Define the topic of the will*/
    .willQoS = 0,                                         /*Defines the class of service for Will messages*/
    .willmsg = "W5100S offline!",                         /*Define a will message*/
    .subQoS = 0,                                         /*Defines the class of service for subscription messages*/
};

MQTTMessage pubmessage = {
        .qos = 0,
        .retained = 0,
        .dup = 0,
        .id = 0,
    };
MQTTPacket_willOptions willdata = MQTTPacket_willOptions_initializer; /* Will subject struct initialization */
MQTTPacket_connectData data = MQTTPacket_connectData_initializer; /*Define the parameters of the MQTT connection*/
unsigned char *data_ptr = NULL;



/**
 * @brief   Initialization of chip network information
 * @param   conf_info :Static configuration information
 * @return  none
 */
void network_init(wiz_NetInfo *conf_info);

wiz_NetInfo net_info = {
    .mac = {0x00, 0x08, 0xdc, 0x16, 0xed, 0x2e}, /*Define the W5100S default mac address*/
    .ip = {192, 168, 1, 123},                   /*Define the W5100S default IP*/
    .sn = {255, 255, 255, 0},                    /*Define the W5100S default subnet mask*/
    .gw = {192, 168, 1, 1},                    /*Define the W5100S default gateway*/
    .dns = {8, 8, 8, 8},                         /*Define the W5100S default DNS server*/
    .dhcp = NETINFO_DHCP};                     /*Define the W5100S as statically acquiring IP mode*/
MQTTClient c = {0};
Network n = {0};
wiz_NetInfo get_info;
static uint8_t breakout_flag = 0; // Define the DHCP acquisition flag
int connOK;

static uint8_t ethernet_buf[ETHERNET_BUF_MAX_SIZE] = {
    0,
}; // Send and receive cachestatic uint8_t destip[4]={192, 168, 1, 2};  // udp destination ip


/*
    @brief Callback processing after triggering the timer.
    @param Timer struct.
    @return True.
 */
bool repeating_timer_mqtt_callback(struct repeating_timer *t);

/*
    @brief Callback processing after triggering the timer.
    @param Timer struct.
    @return True.
 */
bool repeating_timer_dhcp_callback(struct repeating_timer *t);

/*
    @brief MQTT client and connection initialization functions.
    @param None.
    @return None.
 */
void mqtt_init(void);

/*
   @brief Subscribe to the topic callback function.
   @param Message queue.
   @return None.
*/
void messageArrived(MessageData *md);

int main()
{
    int ret;
    struct repeating_timer timer_mqtt;
    struct repeating_timer timer_dhcp;
    MQTTMessage pubmessage = {0};

    /*mcu init*/
    stdio_init_all();                    /*Initialize the serial port*/
    wizchip_initialize();                /*Initialize the SPI*/
    wizchip_setnetinfo(&net_info); // Configure once first
    
   /*dhcp init*/
    DHCP_init(SOCKET_ID, ethernet_buf);                                   // DHCP initialization
    add_repeating_timer_ms(1000, repeating_timer_dhcp_callback, NULL, &timer_dhcp); // Add DHCP 1s Tick Timer handler

    printf("wiznet chip mqtt client example.\r\n");
    network_init(&net_info);              // Configuring Network Information
    print_network_information(&get_info); // Read back the configuration information and print it
    
    /*mqtt init*/
    add_repeating_timer_ms(1, repeating_timer_mqtt_callback, NULL, &timer_mqtt); /*Set a timer for one second*/                                                     /*Initialize MQTT*/
    mqtt_init(); 
    while (true)
    {
       switch (run_status)
        {
         case CONN:
          {
              ret = MQTTConnect(&c, &data); /* Connect to the MQTT server */
            printf("Connect to the MQTT server: %d.%d.%d.%d:%d\r\n", mqtt_params.server_ip[0], mqtt_params.server_ip[1], mqtt_params.server_ip[2], mqtt_params.server_ip[3], mqtt_params.port);
            printf("Connected:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
            if (ret != SUCCESSS)
            {
                run_status = ERROR;
            }
            else
            {
                run_status = SUB;
            }  
            break;
          }

         case SUB:
          {
            ret = MQTTSubscribe(&c, mqtt_params.subtopic, mqtt_params.subQoS, messageArrived); /* Subscribe to Topics */
            printf("Subscribing to %s\r\n", mqtt_params.subtopic);
            printf("Subscribed:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
            if (ret != SUCCESSS)
            {
                run_status = ERROR;
            }
            else
            {
                run_status = PUB_ONLINE;
            }
            run_status = PUB_ONLINE;
            break;

          }
         case PUB_ONLINE:
         {
             pubmessage.qos = 0;
            pubmessage.payload = "W5100S online!";
            pubmessage.payloadlen = strlen(pubmessage.payload);
            ret = MQTTPublish(&c, mqtt_params.willtopic, &pubmessage); /* Publish message */
            if (ret != SUCCESSS)
            {
                run_status = ERROR;
            }
            else
            {
                printf("publish:%s,%s\r\n\r\n", mqtt_params.willtopic, pubmessage.payload);
                run_status = KEEPALIVE;
            }
            break;

         }
           
         case KEEPALIVE:   
         {
           if (MQTTYield(&c, 30) != SUCCESSS) /* keepalive MQTT */
            {
                run_status = ERROR;
            }           
            sleep_ms(100);
            break;
         }            
         case ERROR: /* Running error */
            printf("system ERROR!");
            sleep_ms(1000);
         break;

         default:
            break;
         }
    }
}

bool repeating_timer_mqtt_callback(struct repeating_timer *t)
{
    MilliTimer_Handler();
    return true;
}

bool repeating_timer_dhcp_callback(struct repeating_timer *t)
{
    DHCP_time_handler();
    return true;
}

void mqtt_init(void)
{
    NewNetwork(&n, 1);                                                /*Obtain network configuration information*/
    ConnectNetwork(&n, mqtt_params.server_ip, mqtt_params.port);                  /*Connect to the MQTT server*/
    MQTTClientInit(&c, &n, 1000, mqtt_send_buff, MQTT_SEND_BUFF_SIZE, mqtt_recv_buff, MQTT_RECV_BUFF_SIZE);
    data.willFlag = 1;                                                                                      /* will flag */
    willdata.qos = mqtt_params.willQoS;                                                                     /* will QoS */
    willdata.topicName.lenstring.data = mqtt_params.willtopic;                                              /* will topic */
    willdata.topicName.lenstring.len = strlen(willdata.topicName.lenstring.data);                           /* will topic len */
    willdata.message.lenstring.data = mqtt_params.willmsg;                                                  /* will message */
    willdata.message.lenstring.len = strlen(willdata.message.lenstring.data);                               /* will message len */
    willdata.retained = 0;
    willdata.struct_version = 3;
    data.will = willdata;
    data.MQTTVersion = 4;                           
    data.clientID.cstring = mqtt_params.clientid;
    data.username.cstring = mqtt_params.username;
    data.password.cstring = mqtt_params.passwd;
    data.keepAliveInterval = 30;
    data.cleansession = 1;
    sleep_ms(300);
    
}

void messageArrived(MessageData *md)
{
     int ret;
    char topicname[64] = {0};
    char msg[512] = {0};
    MQTTMessage pubmessage = {
        0,
    };
    sprintf(topicname, "%.*s", (int)md->topicName->lenstring.len, md->topicName->lenstring.data);
    sprintf(msg, "%.*s", (int)md->message->payloadlen, (char *)md->message->payload);
    printf("recv:%s,%s\r\n\r\n", topicname, msg);

    /* Message loopback processing */
    pubmessage.qos = mqtt_params.pubQoS;
    pubmessage.payload = msg; 
    pubmessage.payloadlen = strlen(pubmessage.payload);
    ret = MQTTPublish(&c, mqtt_params.pubtopic, &pubmessage);
    if (ret != SUCCESSS)
    {
        run_status = ERROR;
    }
    else
    {
        printf("publish:%s,%s\r\n\r\n", mqtt_params.pubtopic, msg);
    }
    
}

void network_init(wiz_NetInfo *conf_info)
{
    int count = 0;
    uint8_t dhcp_retry = 0;

    if (conf_info->dhcp == NETINFO_DHCP)
    {
        while (true)
        {
            switch (DHCP_run()) // Do the DHCP client
            {
            case DHCP_IP_LEASED: // DHCP resolves the domain name successfully
            {
                if (breakout_flag == 0)
                {
                    printf("DHCP success\r\n");
                    getIPfromDHCP((*conf_info).ip);
                    getGWfromDHCP((*conf_info).gw);
                    getSNfromDHCP((*conf_info).sn);
                    getDNSfromDHCP((*conf_info).dns);
                    wizchip_setnetinfo(conf_info); // Configuring Network Information
                    close(SOCKET_ID);              // After dhcp close the socket, avoid errors in later use
                    breakout_flag = 1;
                }
                break;
            }
            case DHCP_FAILED:
            {
                printf(" DHCP failed \r\n");
                count++;
                if (count <= DHCP_RETRY_COUNT) // If the number of times is less than or equal to the maximum number of times, try again
                {
                    printf("DHCP timeout occurred and retry %d \r\n", count);
                }
                else if (count > DHCP_RETRY_COUNT) // If the number of times is greater than DHCP fails
                {
                    breakout_flag = 1; // if DHCP fail, use the static
                    DHCP_stop();       // Stop processing DHCP protocol
                    conf_info->dhcp = NETINFO_STATIC;
                    wizchip_setnetinfo(conf_info); // Configuring Network Information
                    break;
                }
                break;
            }
            }
            if (breakout_flag)
            {
                printf("config succ\r\n");
                break;
            }
        }
    }
    else
    {
        wizchip_setnetinfo(conf_info); // Configuring Network Information
    }
}